#include <iostream>
#include <sys/types.h> 
#include <sys/socket.h>
#include "log.hpp"
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
Log lg;

using func_t = std::function<std::string(struct sockaddr_in &client,const std::string&)>;


class udpserver
{
public:
    udpserver(uint16_t port=8080, string ip="192.168.132.128")
        : _port(port), _ip(ip)
    {
    }
    ~udpserver()
    {
        close(_socketfd);
    }

    void Start()
    {
        // 1.创建套接字
        // 第一个参数：创建套接字的类型
        // 第二个：创建套接字时所需的服务类型（字节流、数据报）
        // 第三个：创建套接字的协议类别（一般前两个就可以确定了，可以不写）
        _socketfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socketfd < 0)
        {
            lg(Fatal, "error socket: %s, error code: %d", strerror(errno), errno);
            exit(-1);
        }
        lg(Info, "socket success: %s, error code: %d", strerror(errno), errno);

        // 网络套接字
        struct sockaddr_in local;
        // 初始化结构
        memset(&local, 0, sizeof(struct sockaddr_in));
        local.sin_family = AF_INET;    // 类型
        local.sin_port = htons(_port); // 端口号要转为网络字节序
        // 如果ip地址为空，最好选择宏INADDR_ANY,表示可以绑定任意ip
        local.sin_addr.s_addr =inet_addr(_ip.c_str()); // ip地址 因为一般的串口号输入是字符串 "" ，我们得转换为四字节整数ip
       // local.sin_addr.s_addr = INADDR_ANY;
        // 2.绑定端口号 强转为const struct sockaddr*，函数中识别结构体前16位来确定是网络套接字
        int n = bind(_socketfd, (const struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            lg(Fatal, "error bind: %s, error code: %d", strerror(errno), errno);
            exit(-2);
        }
        lg(Info, "bind success: %s, error code: %d", strerror(errno), errno);
    }

    void Run(func_t func)
    {
        // 服务器一直跑，有消息来就接收消息
        char buffer[1024]; // 缓冲区储存发来的信息
        // 后面两个参数是对端网络相关的属性信息，包括协议家族、IP地址、端口号等 以及结构体长度
        

        while (1)
        {
            struct sockaddr_in recv; //客户端
            socklen_t len = sizeof(recv);

            ssize_t n = recvfrom(_socketfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&recv, &len);
            if (n < 0)
            {
                lg(Warning, "error recvfrom: %s, error code: %d", strerror(errno), errno);
                continue;
            }
            //lg(Info, "recvfrom success: %s, error code: %d", strerror(errno), errno);
            
            //保存在线客户端ip
            string clientip=inet_ntoa(recv.sin_addr);
            uint16_t clientport=ntohs(recv.sin_port);  //
            User_Storage(recv,clientport,clientip);

            //服务器收到消息然后将信息发送给  发送端对应的主机回显
            //后两个参数是对应主机的信息
            if(n>0)
            {
                //可以通过回调函数来处理这个信息
                string s=func(recv,buffer);
                
                //向自己回显
                //sendto(_socketfd,s.c_str(),s.size(),0,(struct sockaddr *)&recv,len);
                //cout<<s<<endl;
                
                //发送给全体用户
                Broadcast(buffer);
                memset(buffer,0,sizeof(buffer));    
            }
        }
    }

    //服务器保存当前访问的ip :使用哈希表来进行保存
    //使用者的IP已经提前知道了的
    //UDP无法知道对方是否退出，因此这里只能添加ip
    void User_Storage(struct sockaddr_in &recv,uint16_t port,string &ip)
    {
        auto it=_user.find(ip); //在哈希表中找
        if(it==_user.end())
        {
            _user.insert(make_pair(ip,recv));
            cout << "[" << ip << ":" << port << "] add to online user." << endl;
        }
        
    }

    //将每个人发送给服务器的消息 发送给全部用户，让大家都看到
    //用户端为了不将自己发送的数据和收到的全部用户的数据混在一起
    //可以输出重定向，单独开一个窗口
    void Broadcast(string info)
    {
        for(const auto &client:_user)
        {
            string name;
            name+="[";
            name+=client.first;
            name+="]:";
            name+=info;
            cout<<name<<endl;
            socklen_t len=sizeof(client.second);
            //最后一个参数是结构体的大小长度
            sendto(_socketfd,name.c_str(),name.size(),0,(struct sockaddr*)(&(client.second)),len);
        }
    }



private:
    int _socketfd;
    uint16_t _port;
    std::string _ip;
    std::unordered_map<std::string,struct sockaddr_in> _user;  //
};
